#include "globals.h"

static void sig_alrm(int signo);
cli_log * cli_info;

int 
max_desc(
		sock_desc arr[],
		int len
	)
{
	int max_v = arr[0].sockfd;
	int ix = 0;

	for (;ix < len; ix++)
	{
		if (max_v < arr[ix].sockfd)
		{
			max_v = arr[ix].sockfd;
		}
	}

	return max_v;
}

void
parse_stale_val(
        char *str,
        int *val
        )
{
    int ix = 0;

    for(ix = 0; ix < strlen(str); ix++)
    {
        if (str[ix] - '0' < 0 || str[ix] - '9' > 9)
        {
            printf("invalid char as arguments.\n");
            exit(1);
        }
    }

    *val = atoi(str);
}

int
main (int argc, char **argv)
{
	struct hwa_info	*hwa, *hwahead;
	struct sockaddr	*sa;
	char   *ptr;
	int    i;
	int ix;
	int jx;
	int num_sockets = 0;
	int err;
	int sockfd_cli, sockfd_srv;
	int len;
	char msg[1000];
	sock_desc sockets[NUM_INTERFACES];
	char my_ip[INET_ADDRSTRLEN];
	struct sockaddr_un odraddr_cli, odraddr_srv, cliaddr;
	int staleness;
	int maxfd;
	fd_set rset;
	int check_stale;

	void* buffer = (void*)malloc(ETH_FRAME_LEN);
	int length = 0;

	int glob_broadcast_id = 0;
	int source_port;
	int dest_port;
	int tmp_port = 10000;
    int alarm_started = 0;

	cli_info = (cli_log *) malloc(sizeof(cli_log));
	cli_info->next = NULL;

	broadcast_list * bl_head;

	table * route_tab = (table *)malloc(sizeof(table));
	route_tab->next = NULL;

	bl_head = (broadcast_list *) malloc (sizeof(broadcast_list));
	bl_head->bl_link = 0;

	RREQ_hdr * rreq_packet = NULL;
	RREP_hdr * rrep_packet = NULL;

    if (argc != 2)
    {
        printf("Usage: ./odr <staleness-in-seconds>.\n");
        exit(1);
    }

    parse_stale_val(argv[1], &staleness);

    sockfd_cli = Socket(AF_LOCAL, SOCK_DGRAM, 0);
	sockfd_srv = Socket(AF_LOCAL, SOCK_DGRAM, 0);

	printf("\n");

	add_client(cli_info, CLI_DG_PATH, 1, 4095);
	printf("Adding permanent entry [%s] [%s] to sun_path table.\n", CLI_DG_PATH, find_client_sunpath(cli_info, 4095));

	FD_ZERO(&rset);

	bzero(&odraddr_cli, sizeof(odraddr_cli));	
	odraddr_cli.sun_family = AF_LOCAL;
	strcpy(odraddr_cli.sun_path, find_client_sunpath(cli_info, 4095));

	unlink(find_client_sunpath(cli_info, 4095));

	err = bind(sockfd_cli, (SA *) &odraddr_cli, sizeof(odraddr_cli));
	check_err(err, "bind sockdgram");

	add_client(cli_info, SRV_DG_PATH, 1, 4096);
	printf("Adding permanent entry [%s] [%s] to sun_path table.\n", CLI_DG_PATH, find_client_sunpath(cli_info, 4096));

	bzero(&odraddr_srv, sizeof(odraddr_srv));	
	odraddr_srv.sun_family = AF_LOCAL;
	strcpy(odraddr_srv.sun_path, find_client_sunpath(cli_info, 4096));

	unlink(find_client_sunpath(cli_info, 4096));

	err = bind(sockfd_srv, (SA *) &odraddr_srv, sizeof(odraddr_srv));
	check_err(err, "bind sockdgram");

	num_sockets = create_raw_sockets(sockets);
    
    Signal(SIGALRM, sig_alrm);

	while (1)
	{   
		char dest_ip_addr[INET_ADDRSTRLEN];

		strcpy(my_ip, get_odr_ip());	

		//
		// Select call symantics.
		// 

		for(ix = 0; ix < num_sockets; ix++)
		{
			FD_SET(sockets[ix].sockfd, &rset);
		}

		FD_SET(sockfd_cli, &rset);	
		FD_SET(sockfd_srv, &rset);

		maxfd = max_desc(sockets, num_sockets);
		maxfd = max(maxfd, sockfd_cli);
		maxfd = max(maxfd, sockfd_srv);
		
		err = select(maxfd + 1, &rset, NULL, NULL, NULL);

		if (err < 0 && errno == EINTR)
		{
//			printf("EINTR error %d\n", err);
			continue;
		}
		else if (err < 0)
		{
			perror("Select Error");
			exit(1);
		}

		for (ix = 0; ix < num_sockets; ix++)
		{
			//
			// One of the raw sockets received a message.
			// 

			if (FD_ISSET(sockets[ix].sockfd, &rset))
			{
				// TODO
				length = recvfrom(sockets[ix].sockfd, buffer, ETH_FRAME_LEN, 0, NULL, NULL);

				if (length == -1) 
				{
					printf("Error in Received Frame\n");
				}
				else
				{
					//
					// First field of all message formats received is the 
					// type of the packet received. Extract it.
					//

					int *iptr = (int *)(buffer + 14);
					int iptr_val = ntohs(*iptr);

					printf("Data received. Message type: %d\n",iptr_val);

					if (iptr_val == 0)
					{
						//
						// RREQ Received.
						//

						process_recvd_rreq(sockets, ix, buffer, bl_head,
								route_tab, my_ip, num_sockets, staleness, rrep_packet); 
					}	
					else if (iptr_val == 1)
					{
						//
						// RREP Recieved.
						//

						process_recvd_rrep(sockets, ix, buffer, route_tab, my_ip, 
								num_sockets, dest_ip_addr, source_port,
								dest_port, "foo");
					}
					else if (iptr_val == 2)
					{
						//
						//Application Payload Received
						//

						printf("Received application data on ifindex: %d\n",
								sockets[ix].ifindex);

						process_recvd_apphdr(sockets, ix, buffer, route_tab,
								my_ip, cli_info, num_sockets);
					}
					else
					{
						err_sys("Unexpected packet received.\n");
						exit(1);
					}
				}

			}
		}	

		//
		// The datagram socket recived a message. Implies that client called
		// msg_send.
		//

		if (FD_ISSET(sockfd_cli, &rset))
		{
			// TODO
			printf("Received msg_send from client.\n");

			length = recvfrom(sockfd_cli, msg, 1000, 0, (SA *) &cliaddr, &len);

			if (length == -1) 
			{
				printf("Error in Received Frame\n");
			}
			else
			{
				int clisockfd;
				char  data[1000]; 
				int disc_flag;
				node * n;
                char cli_sun_path[104];

				//
				// Parse the character stream received from client.
				//

				printf("Generating RREQ from received data.\n");

				strcpy(data, parse_msg_send_data(msg, &clisockfd, dest_ip_addr,
							&dest_port, &disc_flag, cli_sun_path)); 

				if (find_client(cli_info, cli_sun_path) == 0)
				{
					printf("Adding a temporary entry to the sun_path table: %s.\n", 
							cli_sun_path);

					source_port = tmp_port;
                    
                    if (alarm_started == 0)
                    {
                        //
                        // Adding a temporary entry. Set a time out to delete
                        // expired entries as well.
                        //

                        alarm_started = 1;
                        alarm (CLIENT_TIMEOUT + 1);
                    }

					add_client(cli_info, cli_sun_path, 0, tmp_port++);

				}
				else
				{
					printf("Updating the temporary entry in the sun_path table: %s.\n",
							cli_sun_path);

					update_client(cli_info, cli_sun_path);
				}

				n = find_entry(route_tab, dest_ip_addr);


				if(n == NULL || disc_flag==1 || (n != NULL && (check_stale =  isstale(n->timestamp, staleness, n->dest))))				
				{
                    if (disc_flag == 1)
                    {
                        printf("Forced route discovery flag set.\n");
                    }

					if(n != NULL && check_stale)
					{
						//
						// Remove the stale entry
						//			
						printf("Stale Entry for Destination %s has been removed from the Routing table\n", n->dest);
						remove_entry(route_tab, n->dest);
					}					
					//
					// Generate the RREQ.
					//

					printf("Destination not found in Routing Table ... Broadcasting RREQ\n");

					rreq_packet = generate_rreq(get_odr_ip(), dest_ip_addr,
							&glob_broadcast_id, disc_flag);

					//
					// Add a entry in the Broadcast List table
					//
					insert_bl(
							create_br(my_ip, ntohs(rreq_packet->broadcast_id)), bl_head);

					flood_all_interfaces(sockets, num_sockets, (void*) rreq_packet,
							sizeof(*rreq_packet));

				}
				else
				{
					//
					// Destination found in Routing Table
					//

					APP_hdr * app_data = create_app_data(my_ip, source_port,
							dest_ip_addr, dest_port, n->hop_count,
							strlen(data), data);

					printf("\nDestination found in Routing Table ... Unicasting Data to Destination\n");

					for(i = 0; i < num_sockets ; i++)
					{
						if(sockets[i].ifindex == n->ifindex)
						{
							break;
						}	
					}

					printf("\nSending application data packet to the next hop on interface: %d.\n", 
							sockets[i].ifindex);

					send_unicast(sockets[i].sockfd, sockets[i].mac_addr, n->next_hop,
							sockets[i].ifindex, (void*) app_data, sizeof(APP_hdr));
				}

			}
		}

		if(FD_ISSET(sockfd_srv, &rset))
		{
			printf("Received msg_send from Server.\n");

			length = recvfrom(sockfd_srv, msg, 1000, 0, (SA *) &cliaddr, &len);

			if (length == -1) 
			{
				printf("Error in Received Frame\n");
			}
			else
			{
				int clisockfd;
				char  data[1000]; 
				int disc_flag;
				node * n;
                char cli_sun_path[104];

				//
				// Parse the character stream received from client.
				//

				strcpy(data, parse_msg_send_data(msg, &clisockfd, dest_ip_addr,
							&dest_port, &disc_flag, cli_sun_path)); 
                
                n = find_entry(route_tab, dest_ip_addr);

                APP_hdr * app_data = create_app_data(my_ip, 4094,
						dest_ip_addr, dest_port, n->hop_count,
						strlen(data), data);

				printf("\nDestination found in Routing Table ... Unicasting Data to Destination\n");

				for(i = 0; i < num_sockets ; i++)
				{
					if(sockets[i].ifindex == n->ifindex)
					{
						break;
					}	
				}

				printf("\nSending application data packet to the next hop on interface: %d %s %s.\n", 
						sockets[i].ifindex, my_ip, dest_ip_addr);

				send_unicast(sockets[i].sockfd, sockets[i].mac_addr, n->next_hop,
						sockets[i].ifindex, (void*) app_data, sizeof(APP_hdr));

			}

		}
	}	

	free_hwa_info(hwahead);
	exit(0);
}

static void
sig_alrm(int signo)
{
    //
    // Cleanup the table.
    //

    cleanup_client_table(cli_info);
    alarm(0);
    alarm (CLIENT_TIMEOUT + 1);
}


